import {
  EdgeFragment,
  EdgeVertex,
  MaskFragment,
  MaskVertex,
  OverlayFragment,
  PrepareMaskFragment,
  PrepareMaskVertex,
  SeperableBlurFragment,
  SeperableBlurVertex,
} from '../shader/shader_outline_helper.js';
import * as THREE from "../../../../modules/three/three.module.js";
import {FXAAShader} from "../../../../modules/three/jsm/shaders/FXAAShader.js";

const oldClearColor = new THREE.Color();
const BlurDirectionX = new THREE.Vector2(1.0, 0.0);
const BlurDirectionY = new THREE.Vector2(0.0, 1.0);
const MAX_EDGE_THICKNESS = 4;
const MAX_EDGE_GLOW = 4;
const textureMatrix = new THREE.Matrix4();

function replaceDepthToViewZ(string, camera) {
  const type = camera.isPerspectiveCamera ? 'perspective' : 'orthographic';
  return string.replace(/DEPTH_TO_VIEW_Z/g, type + 'DepthToViewZ');
}

class GlowHelper {
  /**
   * 被隐藏的对象
   * @type {THREE.Object3D[]}
   */
  hideObjects = [];
  /**
   * 选中对象
   * @type {THREE.Object3D[]}
   */
  selectedObjects = [];
  /**
   * 鼠标hover对象
   * @type {THREE.Object3D[]}
   */
  hoverObjects = [];

  /**
   * 用于显示3d模型的轮廓
   * @param {THREE.WebGLRenderer} renderer
   * @param {THREE.Scene} renderScene
   * @param {THREE.Camera} renderCamera
   */
  constructor(renderer, renderScene, renderCamera) {
    this.renderer = renderer;
    this.renderScene = renderScene;
    this.renderCamera = renderCamera;

    const scene = new THREE.Scene();
    scene.matrixWorldAutoUpdate = false;
    const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);
    const _geometry = new THREE.BufferGeometry();
    _geometry.setAttribute(
      'position',
      new THREE.Float32BufferAttribute([-1, 3, 0, -1, -1, 0, 3, -1, 0], 3),
    );
    _geometry.setAttribute(
      'uv',
      new THREE.Float32BufferAttribute([0, 2, 0, 0, 2, 0], 2),
    );
    const quad = new THREE.Mesh(_geometry, null);
    this._quad = quad;
    scene.add(quad);
    this._scene = scene;
    this._camera = camera;

    this._maskMaterial = new THREE.ShaderMaterial({
      vertexShader: MaskVertex,
      fragmentShader: MaskFragment,
      depthTest: false,
      side: THREE.DoubleSide,
    });
    this.onCanvasResize();
    // 对外可编辑参数
    this.edgeThickness = 1;
    this.edgeGlow = 2.7;
    this.edgeStrength = 3.7;
  }

  setParams(thickness, glow, strength) {
    this.edgeThickness = thickness;
    this.edgeGlow = glow;
    this.edgeStrength = strength;
  }

  setRenderCamera(camera) {
    this.renderCamera = camera;
  }

  _hideSelectedObjects() {
    this.selectedObjects.forEach((obj) => {
      obj.userData.oldVisible = obj.visible;
      this.hideObjects.push(obj);
      obj.visible = false;
    });
  }

  _hideNonSelectedObjects(obj, root, selected) {
    if (selected.includes(obj)) {
      let current = obj.parent;
      while (current && current !== root) {
        const index = this.hideObjects.indexOf(current);
        this.hideObjects.splice(index, 1);
        current.visible = current.userData.oldVisible;
        delete current.userData.oldVisible;
        current = current.parent;
      }
      return;
    }

    if (obj !== root) {
      obj.userData.oldVisible = obj.visible;
      obj.visible = false;
      this.hideObjects.push(obj);
    }

    for (const child of obj.children) {
      if (child instanceof THREE.Light) {
        continue;
      }
      this._hideNonSelectedObjects(child, root, selected);
    }
  }

  _unhideObjects() {
    this.hideObjects.forEach((n) => {
      if (n.userData.oldVisible !== undefined) {
        n.visible = n.userData.oldVisible;
        delete n.userData.oldVisible;
      }
    });
  }

  onCanvasResize() {
    const size = new THREE.Vector2();
    this.renderer.getDrawingBufferSize(size);
    const width = size.x * 2;
    const height = size.y * 2;
    const params = {
      minFilter: THREE.LinearFilter,
      magFilter: THREE.LinearFilter,
      format: THREE.RGBAFormat,
      antialias: true,
    };
    this._depthBuffer = new THREE.WebGLRenderTarget(width, height, params);
    this._depthBuffer.texture.generateMipmaps = false;
    this._maskBuffer = new THREE.WebGLRenderTarget(width, height, params);
    this._maskBuffer.texture.generateMipmaps = false;
    this._maskBufferHover = new THREE.WebGLRenderTarget(width, height, params);
    this._maskBufferHover.texture.generateMipmaps = false;
    this._overlayBuffer = new THREE.WebGLRenderTarget(width, height, params);
    this._overlayBuffer.texture.generateMipmaps = false;
    this._edgeBuffer1 = new THREE.WebGLRenderTarget(width, height, params);
    this._edgeBuffer1.texture.generateMipmaps = false;
    this._edgeBuffer2 = new THREE.WebGLRenderTarget(
      width / 2,
      height / 2,
      params,
    );
    this._edgeBuffer2.texture.generateMipmaps = false;

    this._renderTargetBlurBuffer1 = new THREE.WebGLRenderTarget(
      width,
      height,
      params,
    );
    this._renderTargetBlurBuffer1.texture.generateMipmaps = false;
    this._renderTargetBlurBuffer2 = new THREE.WebGLRenderTarget(
      width / 2,
      height / 2,
      params,
    );
    this._renderTargetBlurBuffer2.texture.generateMipmaps = false;

    this._depthMaterial = new THREE.MeshDepthMaterial();
    this._depthMaterial.side = THREE.DoubleSide;
    this._depthMaterial.depthPacking = THREE.RGBADepthPacking;
    this._depthMaterial.blending = THREE.NoBlending;
    this._prepareMaskMaterial = new THREE.ShaderMaterial({
      uniforms: {
        depthTexture: {
          value: null, // this._maskBuffer.texture
        },
        cameraNearFar: {
          value: new THREE.Vector2(0.5, 0.5),
        },
        textureMatrix: {
          value: null,
        },
      },
      vertexShader: PrepareMaskVertex,
      fragmentShader: replaceDepthToViewZ(
        PrepareMaskFragment,
        this.renderCamera,
      ),
      side: THREE.DoubleSide,
    });

    const selectedColor = '#fff';
    const hoverColor = '#966b1b';
    const selectedThickness = 0.6;
    this._edgeMaterial = new THREE.ShaderMaterial({
      vertexShader: EdgeVertex,
      // fragmentShader: EdgeFragmentV2,
      fragmentShader: EdgeFragment,
      uniforms: {
        maskTexture: {
          value: this._maskBuffer.texture,
        },
        maskTextureHover: {
          value: this._maskBufferHover.texture,
        },
        texSize: {
          value: new THREE.Vector2(width, height),
        },
        visibleEdgeColor: {
          value: new THREE.Color(selectedColor),
        },
        hiddenEdgeColor: {
          value: new THREE.Color('#09f1e0'),
        },
        hoverColor: {
          value: new THREE.Color(hoverColor),
        },
        thickness: {
          type: 'f',
          value: selectedThickness,
        },
      },
      depthTest: false,
      toneMapped: false,
      side: THREE.DoubleSide,
    });

    this._seperateBlurMaterial1 = this._getSeperableBlurMaterial(
      MAX_EDGE_THICKNESS,
      width,
      height,
    );
    this._seperateBlurMaterial1.uniforms.kernelRadius.value = 1;
    this._seperateBlurMaterial2 = this._getSeperableBlurMaterial(
      MAX_EDGE_GLOW,
      width / 2,
      height / 2,
    );
    this._seperateBlurMaterial2.uniforms.kernelRadius.value = MAX_EDGE_GLOW;

    this._overlayMaterial = new THREE.ShaderMaterial({
      vertexShader: EdgeVertex,
      fragmentShader: OverlayFragment,
      uniforms: {
        maskTexture: {
          value: null,
        },
        edgeTexture1: {
          value: null,
        },
        edgeTexture2: {
          value: null,
        },
        edgeStrength: {
          value: 0.8,
        },
        edgeGlow: {
          value: 3.9,
        },
      },
    });

    this._copyMaterial = new THREE.ShaderMaterial({
      vertexShader: FXAAShader.vertexShader,
      fragmentShader: FXAAShader.fragmentShader,
      uniforms: {
        tDiffuse: {
          value: this._overlayBuffer.texture,
        },
        resolution: {
          value: new THREE.Vector2(1 / width, 1 / height),
        },
      },
      blending: THREE.NormalBlending,
      transparent: true,
      depthTest: false,
      toneMapped: false,
      side: THREE.DoubleSide,
    });
  }

  _getSeperableBlurMaterial(maxRadius, width, height) {
    return new THREE.ShaderMaterial({
      vertexShader: SeperableBlurVertex,
      fragmentShader: SeperableBlurFragment,
      defines: {
        MAX_RADIUS: maxRadius,
      },
      uniforms: {
        colorTexture: {
          value: null,
        },
        texSize: {
          value: new THREE.Vector2(width, height),
        },
        direction: {
          value: new THREE.Vector2(0.5, 0.5),
        },
        kernelRadius: {
          value: 1.0,
        },
      },
      depthTest: false,
      toneMapped: false,
      side: THREE.DoubleSide,
    });
  }

  select(obj) {
    if (obj && !this.selectedObjects.includes(obj)) {
      this.selectedObjects.push(obj);
    }
  }

  selectSingle(obj) {
    this.selectedObjects = [obj];
  }

  unSelect() {
    this.selectedObjects = [];
  }
  unSelectItem(item) {
    if (!item) {
      return;
    }
    const index = this.selectedObjects.indexOf(item);
    if (index >= 0) {
      this.selectedObjects.splice(index, 1);
    }
  }

  setHoverSingle(obj) {
    if (!this.selectedObjects.includes(obj)) {
      this.hoverObjects = [obj];
    }
  }

  unHover() {
    this.hoverObjects = [];
  }

  _drawDepth() {
    this._hideSelectedObjects();
    this.renderScene.overrideMaterial = this._depthMaterial;
    this.renderer.autoClear = false;
    this.renderer.setClearColor(0xffffff, 1);
    this.renderer.setRenderTarget(this._depthBuffer);
    this.renderer.clear();
    this.renderer.render(this.renderScene, this.renderCamera);
    this._unhideObjects();
  }

  _drawPrepareMask() {
    this.hideObjects.length = 0;
    this._hideNonSelectedObjects(
      this.renderScene,
      this.renderScene,
      this.selectedObjects,
    );
    this.renderScene.overrideMaterial = this._prepareMaskMaterial;
    this._prepareMaskMaterial.uniforms.cameraNearFar.value.set(
      this.renderCamera.near,
      this.renderCamera.far,
    );
    this._prepareMaskMaterial.uniforms.depthTexture.value =
      this._depthBuffer.texture;
    this._prepareMaskMaterial.uniforms.textureMatrix.value =
      this._getTextureMatrix();
    this.renderer.autoClear = false;
    this.renderer.setRenderTarget(this._maskBuffer);
    this.renderer.clear();
    this.renderer.render(this.renderScene, this.renderCamera);
    this._unhideObjects();
  }

  _drawMask(buffer, visibleObjects) {
    this.hideObjects.length = 0;
    this._hideNonSelectedObjects(
      this.renderScene,
      this.renderScene,
      visibleObjects,
    );

    this.renderScene.overrideMaterial = this._maskMaterial;
    this.renderScene.background = null;

    this.renderer.autoClear = false;
    this.renderer.setRenderTarget(buffer);
    this.renderer.setClearColor(0xffffff);
    this.renderer.setClearAlpha(1);
    this.renderer.clear();

    this.renderer.render(this.renderScene, this.renderCamera);

    this._unhideObjects();
    this.hideObjects.length = 0;
  }

  _drawEdge() {
    this._quad.material = this._edgeMaterial;

    this.renderScene.overrideMaterial = null;

    this.renderer.setRenderTarget(this._edgeBuffer1);
    this.renderer.clear();
    this.renderer.render(this._scene, this._camera);
  }

  _drawBlurEdge() {
    this._quad.material = this._seperateBlurMaterial1;
    this._seperateBlurMaterial1.uniforms.colorTexture.value =
      this._edgeBuffer1.texture;
    this._seperateBlurMaterial1.uniforms.direction.value = BlurDirectionY;
    this.renderer.setRenderTarget(this._renderTargetBlurBuffer1);
    this.renderer.clear();
    this.renderer.render(this._scene, this._camera);

    this._seperateBlurMaterial1.uniforms.colorTexture.value =
      this._renderTargetBlurBuffer1.texture;
    this._seperateBlurMaterial1.uniforms.direction.value = BlurDirectionX;
    this._seperateBlurMaterial1.uniforms.kernelRadius.value =
      this.edgeThickness;
    this.renderer.setRenderTarget(this._edgeBuffer1);
    this.renderer.clear();
    this.renderer.render(this._scene, this._camera);

    this._quad.material = this._seperateBlurMaterial2;
    this._seperateBlurMaterial2.uniforms.colorTexture.value =
      this._edgeBuffer1.texture;
    this._seperateBlurMaterial2.uniforms.direction.value = BlurDirectionY;
    this.renderer.setRenderTarget(this._renderTargetBlurBuffer2);
    this.renderer.clear();
    this.renderer.render(this._scene, this._camera);

    this._seperateBlurMaterial2.uniforms.colorTexture.value =
      this._renderTargetBlurBuffer2.texture;
    this._seperateBlurMaterial2.uniforms.direction.value = BlurDirectionX;
    this.renderer.setRenderTarget(this._edgeBuffer2);
    this.renderer.clear();
    this.renderer.render(this._scene, this._camera);
  }

  _drawOverlay() {
    this._quad.material = this._overlayMaterial;
    this._overlayMaterial.uniforms.maskTexture.value = this._maskBuffer.texture;
    this._overlayMaterial.uniforms.edgeTexture1.value =
      this._edgeBuffer1.texture;
    this._overlayMaterial.uniforms.edgeTexture2.value =
      this._edgeBuffer2.texture;
    this._overlayMaterial.uniforms.edgeGlow.value = this.edgeGlow;
    this._overlayMaterial.uniforms.edgeStrength.value = this.edgeStrength;
    this.renderer.setRenderTarget(this._overlayBuffer);
    this.renderer.clear();
    this.renderer.render(this._scene, this._camera);
  }

  _getTextureMatrix() {
    textureMatrix.set(
      0.5,
      0.0,
      0.0,
      0.5,
      0.0,
      0.5,
      0.0,
      0.5,
      0.0,
      0.0,
      0.5,
      0.5,
      0.0,
      0.0,
      0.0,
      1.0,
    );
    textureMatrix.multiply(this.renderCamera.projectionMatrix);
    textureMatrix.multiply(this.renderCamera.matrixWorldInverse);
    return textureMatrix;
  }

  onAfterRender() {
    if (this.selectedObjects.length === 0 && this.hoverObjects.length === 0) {
      return;
    }
    // 记录原始状态
    const oldOverrideMaterial = this.renderScene.overrideMaterial;
    const oldBackground = this.renderScene.background;

    const oldAutoClear = this.renderer.autoClear;
    this.renderer.getClearColor(oldClearColor);
    const oldClearAlpha = this.renderer.getClearAlpha();
    const oldRenderTarget = this.renderer.getRenderTarget();

    // this._drawDepth()
    // this._drawPrepareMask()
    // 绘制蒙版
    this._drawMask(this._maskBuffer, this.selectedObjects);

    // 绘制蒙版
    this._drawMask(this._maskBufferHover, this.hoverObjects);

    // 绘制边框
    this._drawEdge();

    // blur
    this._drawBlurEdge();
    // overlay
    this._drawOverlay();

    // 与原场景叠加
    this._quad.material = this._copyMaterial;

    this.renderer.setRenderTarget(null);
    this.renderer.render(this._scene, this._camera);

    // 还原原始状态
    this.renderScene.overrideMaterial = oldOverrideMaterial;
    this.renderScene.background = oldBackground;

    this.renderer.autoClear = oldAutoClear;
    this.renderer.setClearColor(oldClearColor);
    this.renderer.setClearAlpha(oldClearAlpha);
    this.renderer.setRenderTarget(oldRenderTarget);
  }
}

export default GlowHelper;
